﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.ComponentModel;
using System.Reflection;
using System.Collections.ObjectModel;

using IrisGenerator.Framework.UI.Controls;
using IrisGenerator.Framework.Reflection;
using IrisGenerator.Framework.Messaging;
using IrisGenerator.Framework.RuleEngine;

namespace IrisGenerator.Framework.UI.Controls
{
    [Serializable()]
    public abstract class BindableDataGrid<T> : DataGridView, IBindableControl
    {

        #region Fields

        /// <summary>
        /// Flag that notifies wether data was already loaded when the control is created
        /// </summary>
        private bool _calledOnLoad;

        #endregion

        #region Properties

        /// <summary>
        /// Defines the method that will be used as the data source of the grid.
        /// </summary>
        public MethodCall SourceMethod { get; set; }

        /// <summary>
        /// Implementation of the IBaseControl interface.
        /// </summary>
        /// <remarks>
        /// The property defines the kind of object the grid will bind to.
        /// The LoadSource method will attempt to create an instance of the type passing
        /// the result of the SourceMethod call as a parameter to the constructor.
        /// </remarks>
        public string BindingType { get; set; }

        #endregion

        #region Overriding

        /// <summary>
        /// Overrides the base method to call the CanUpdate of the object type used in the datasource. 
        /// </summary>
        /// <remarks>
        /// The call to the CanUpdate method works if the following conditions are respected:
        /// <list type="bullet">
        /// <item>The object contained within the data source has to implement IRuleable interface.</item>
        /// <item>The Rules property type of the object has to implement IDbBindableObjectRules interface</item>
        /// </list>
        /// If the CanUpdate method returns false, error messages are displayed in the tooltip of the row.
        /// The validation is then canceled.
        /// </remarks>
        /// <param name="e">The event parameter.</param>
        protected override void OnRowValidating(DataGridViewCellCancelEventArgs e)
        {
            RuleErrorCollection errors = new RuleErrorCollection();
            T obj = (T)this.Rows[e.RowIndex].DataBoundItem;

            if (obj != null)
            {
                IRuleable<T> ruleInterface = obj as IRuleable<T>;
                if (ruleInterface != null)
                {
                    IDbBindableObjectRules bindingRules = ruleInterface.Rules as IDbBindableObjectRules;
                    if (bindingRules != null)
                    {

                        if (!bindingRules.CanUpdate(ref errors) && !bindingRules.CanInsert(ref errors))
                        {
                            e.Cancel = true;
                            string errorText = string.Empty;
                            int j = 0;
                            for (int i = 0; i < errors.Count; i++)
                            {
                                if (i == errors.Count - 1)
                                    errorText += errors[i].Message;
                                else
                                    errorText += errors[i].Message + Environment.NewLine;
                            }
                            this.Rows[e.RowIndex].ErrorText = errorText;
                        }
                        else
                            this.Rows[e.RowIndex].ErrorText = string.Empty;
                    }
                    else
                    {
                        this.Rows[e.RowIndex].ErrorText = string.Empty;
                    }
                }
                else
                {
                    this.Rows[e.RowIndex].ErrorText = string.Empty;
                }
            }
            else
                this.Rows[e.RowIndex].ErrorText = string.Empty;

            base.OnRowValidating(e);
        }

        /// <summary>
        /// Overriden to make a call to the LoadSource method on the control creation.
        /// </summary>
        protected override void OnCreateControl()
        {
            base.OnCreateControl();
            if (!_calledOnLoad)
            {
                this._calledOnLoad = true;
                this.LoadSource();
            }
        }

        /// <summary>
        /// Handles data errors to show a messagebox dialog.
        /// </summary>
        /// <param name="displayErrorDialogIfNoHandler"></param>
        /// <param name="e"></param>
        protected override void OnDataError(bool displayErrorDialogIfNoHandler, DataGridViewDataErrorEventArgs e)
        {
            displayErrorDialogIfNoHandler = false;

            string content = "An error occured while loading data." + Environment.NewLine;
            content += "Column Index: " + e.ColumnIndex.ToString() + Environment.NewLine;
            content += "Row Index: " + e.RowIndex.ToString();
            if (e.Exception != null)
            {
                content += Environment.NewLine + "Inner Exception: " + Environment.NewLine + Environment.NewLine;
                content += "Message: " + Environment.NewLine + e.Exception.Message + Environment.NewLine;
                content += "Stack Trace" + Environment.NewLine + e.Exception.StackTrace;
            }

            string caption = "Data Error";

            IrisGenerator.Framework.Messaging.Message message = new IrisGenerator.Framework.Messaging.Message(content, caption, MessageButtons.Ok, MessageIcon.Error, MessageDefaultButton.Button1);

            MessageToMessageBox.Show(message);

            base.OnDataError(displayErrorDialogIfNoHandler, e);
        }

        #endregion

        #region Message Handling

        private IMessageSender<T> obj = null;

        /// <summary>
        /// If the base object of the data source implements the IrisGenerator.Common.Core.Messaging.IMessageSender`1 interface
        /// the MessageSend event will be handled in the MessageReceived method.
        /// </summary>
        protected virtual void SetMessageHandler()
        {
            if (this.DataSource == null)
                return;
            
            obj = this.DataSource as IMessageSender<T>;
            if (obj != null)
                obj.MessageSend += new EventHandler<MessageEventArgs<T>>(MessageReceived);
        }

        /// <summary>
        /// Handles MessageSend events fired from the objects in the datasource
        /// </summary>
        /// <remarks>
        /// Makes a call to the MessageToMessageBox.Show method passing the message in the event argument.
        /// </remarks>
        /// <param name="sender">The object that fired the message.</param>
        /// <param name="e">The message argument that was sent.</param>
        protected virtual void MessageReceived(object sender, MessageEventArgs<T> e)
        {
            if (e != null)
            {
                e.Message.Result = MessageToMessageBox.Show(e.Message);
            }
        }

        #endregion

        #region Source loading

        /// <summary>
        /// Loads the data source of the grid.
        /// </summary>
        /// <remarks>
        /// If a SourceMethod object is defined, the method is invoked. If a Binding type is defined, then
        /// the source is changed to this type. The method tries to call the constructor of the BindigType
        /// passing it the result of the previous method call as a parameter.
        /// </remarks>
        public virtual void LoadSource()
        {
            object targetSource = null;
            
            if (this.SourceMethod != null && !this.DesignMode)
            {
                object methodCallResult = GetDataSourceFromMethod();
                if (methodCallResult != null)
                {
                    ConstructorInfo ctor = GetBindingTypeConstructor(methodCallResult);

                    if (ctor != null)
                    {
                        object[] parameters = new object[1] { methodCallResult };
                        targetSource = ctor.Invoke(parameters);
                    }
                    else
                    {
                        targetSource = methodCallResult;
                    }
                }
                else
                    targetSource = null;
            }

            this.DataSource = targetSource;


            
            
            SetMessageHandler();
        }

       
        

        private ConstructorInfo GetBindingTypeConstructor(object methodCallResult)
        {
            ConstructorInfo ctor = null;
            if (!string.IsNullOrEmpty(this.BindingType))
            {
                Type destinationType = Type.GetType(this.BindingType);
                if (destinationType != null)
                {
                    
                    if (destinationType.IsGenericTypeDefinition)
                    {
                        Type o = destinationType.MakeGenericType(new Type[1] { typeof(T) });
                        ctor = o.GetConstructor(new Type[1] { typeof(IList<T>) });
                    }
                    else
                    {
                        Type[] parameters = new Type[1];
                        parameters[0] = methodCallResult.GetType();
                        ctor = destinationType.GetConstructor(parameters);
                    }
                }
            }
            return ctor;
        }
        
        /// <summary>
        /// Invokes the method defined in the SourceMethod Property
        /// </summary>
        /// <returns></returns>
        private object GetDataSourceFromMethod()
        {
            bool result;
            object tmp=SourceMethod.Invoke(this,out result);
            return tmp;
        }

        #endregion

    }

}
